// 
// Copyright (c) 2013 Jason Bell
// 
// Permission is hereby granted, free of charge, to any person obtaining a 
// copy of this software and associated documentation files (the "Software"), 
// to deal in the Software without restriction, including without limitation 
// the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the 
// Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included 
// in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.
// 

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;

namespace Gharen.GLCore.X11
{
	public class X11Context<T>
		: GLContext<T>
			where T: GLBase
	{
		IntPtr handle;
		IntPtr display;
		IntPtr context;
		
		X11Window window;
		
		public X11Context ()
			: base(null)
		{
            // Determine the desired version from the type of this generic.
            Version desiredVersion;
            if (this is GLContext<GL30>)
                desiredVersion = new Version(3, 0);
            else if (this is GLContext<GL31>)
                desiredVersion = new Version(3, 1);
            else if (this is GLContext<GL32>)
                desiredVersion = new Version(3, 2);
            else if (this is GLContext<GL33>)
                desiredVersion = new Version(3, 3);
            else if (this is GLContext<GL40>)
                desiredVersion = new Version(4, 0);
            else if (this is GLContext<GL41>)
                desiredVersion = new Version(4, 1);
            else if (this is GLContext<GL42>)
                desiredVersion = new Version(4, 2);
            else if (this is GLContext<GLES20>)
                desiredVersion = new Version(3, 0);
            else if (this is GLContext<GLES30>)
                desiredVersion = new Version(3, 0);
            else
                throw new InitialisationFailedException("OpenGL version is not supported by this library.");

			this.display = X.OpenDisplay(IntPtr.Zero);
			if(this.display == IntPtr.Zero)
				throw new Exception("Error opening X11 display.");
			
			int major = 0;
			int minor = 0;
			if(!glx.QueryVersion(this.display, ref major, ref minor))
				throw new Exception("Error querying glx version.");
			
			if(major < 1 || (major == 1 && minor < 3))
				throw new Exception("glx 1.3 or higher is required.");
			
			int [] attribs =
			{
				(int)GLXAttribute.X_RENDERABLE, 1,
				(int)GLXAttribute.DRAWABLE_TYPE, (int)GLXDrawableTypeMask.WINDOW_BIT,
				(int)GLXAttribute.RENDER_TYPE, (int)GLXRenderTypeMask.RGBA_BIT,
				(int)GLXAttribute.X_VISUAL_TYPE, (int)GLXAttribute.TRUE_COLOR,
				(int)GLXAttribute.RED_SIZE, 8,
				(int)GLXAttribute.GREEN_SIZE, 8,
				(int)GLXAttribute.BLUE_SIZE, 8,
				(int)GLXAttribute.ALPHA_SIZE, 8,
				(int)GLXAttribute.DEPTH_SIZE, 24,
				(int)GLXAttribute.STENCIL_SIZE, 8,
				(int)GLXAttribute.DOUBLEBUFFER, 1,	
				(int)GLXAttribute.SAMPLE_BUFFERS, 0,
				(int)GLXAttribute.SAMPLES, 0,
				(int)GLXAttribute.CONFIG_CAVEAT, (int)GLXAttribute.NONE,
				0
			};
			
			unsafe
			{
				int fbcount = 0;
				IntPtr * fbconfigs = glx.ChooseFBConfig(this.display, X.DefaultScreen(this.display), attribs, out fbcount);
				if(fbconfigs == null)					
					throw new Exception("Error obtaining framebuffer config.");
				
				IntPtr fbconfig = fbconfigs[0];
				
				IntPtr visualInfoPtr = glx.GetVisualFromFBConfig(this.display, fbconfig);
				
				//X.Free (*fbconfigs);
			
				XVisualInfo visualInfo = (XVisualInfo)Marshal.PtrToStructure (visualInfoPtr, typeof(XVisualInfo));
			
				var swa = new XSetWindowAttributes();
				swa.colormap = X.CreateColormap(this.display, X.RootWindow (this.display, visualInfo.Screen),
									visualInfo.Visual, 0);
				swa.background_pixel = IntPtr.Zero;
				swa.border_pixel = IntPtr.Zero;
				swa.event_mask = (IntPtr)(EventMask.StructureNotifyMask | EventMask.ExposureMask |
					EventMask.KeyReleaseMask | EventMask.KeyPressMask | EventMask.KeymapStateMask |
						EventMask.PointerMotionMask | EventMask.FocusChangeMask |
						EventMask.ButtonPressMask | EventMask.ButtonReleaseMask |
						EventMask.EnterWindowMask | EventMask.LeaveWindowMask |
						EventMask.PropertyChangeMask);
				
				uint mask = (uint)(SetWindowValuemask.ColorMap | 
				                   SetWindowValuemask.EventMask |
				                   SetWindowValuemask.BackPixel |
				                   SetWindowValuemask.BorderPixel);
				
				this.handle = X.CreateWindow(this.display, 
				                             X.RootWindow (this.display, visualInfo.Screen),
				                             0, 0, 640, 480, 
				                             0, 
				                             visualInfo.Depth, 
				                             (int)CreateWindowArgs.InputOutput, 
				                             visualInfo.Visual,
				                             (UIntPtr)mask,
				                             ref swa);
				
				if(this.handle == IntPtr.Zero)
					throw new Exception("Error creating window.");
				
				X.Free (visualInfoPtr);
				
				X.StoreName (this.display, this.handle, "RAWR!!!");	
				
				X.MapWindow (this.display, this.handle);
				
				string glxex = glx.QueryExtensionsString(this.display, visualInfo.Screen);
				
				IntPtr fptr = glx.GetProcAddress("glXCreateContextAttribsARB");
				if(fptr == IntPtr.Zero)
					throw new Exception("Cannot get pointer to glXCreateContextAttribsARB");
				
				Delegates.glXCreateContextAttribsARB = (Delegates.CreateContextAttribsARB)Marshal.GetDelegateForFunctionPointer(fptr, typeof(Delegates.CreateContextAttribsARB));
			
				// Error handling stuff?
				
				/*if(!glxex.Contains ("GLX_ARB_create_context"))
				   throw new Exception("Unable to create an OpenGL 3.0+ context.");
				
				int [] context_attribs =
				{
					(int)ArbCreateContext.MajorVersion, 3,
					(int)ArbCreateContext.MinorVersion, 0,
					0
				};
				
				IntPtr context = glx.CreateContextAttribs(this.display,
				                                          fbconfig,
				                                          IntPtr.Zero,
				                                          true,
				                                          context_attribs);*/
				//XVisualInfo vi = new XVisualInfo();
				//vi.BitsPerRgb = 32;
				
				this.context = glx.CreateContext(this.display, 
									ref visualInfo, 
				                    IntPtr.Zero,
				                    true);
				if(context == IntPtr.Zero)
					throw new Exception("Error creating context.");
				
				if(!glx.MakeCurrent(this.display, this.handle, context))
					throw new Exception("Error making context current.");
				
				this.window = new X11Window(this.display, this.handle);

                // Create a GL object for the chosen version.
                if (this is GLContext<GL30>)
                    this.GL = (T)((GLBase)new GL30(this));
                if (this is GLContext<GL31>)
                    this.GL = (T)((GLBase)new GL31(this));
                if (this is GLContext<GL32>)
                    this.GL = (T)((GLBase)new GL32(this));
                if (this is GLContext<GL33>)
                    this.GL = (T)((GLBase)new GL33(this));
                if (this is GLContext<GL40>)
                    this.GL = (T)((GLBase)new GL40(this));
                if (this is GLContext<GL41>)
                    this.GL = (T)((GLBase)new GL41(this));
                if (this is GLContext<GL42>)
                    this.GL = (T)((GLBase)new GL42(this));
                if (this is GLContext<GLES20>)
                    this.GL = (T)((GLBase)new GLES20(this));
                if (this is GLContext<GLES30>)
                    this.GL = (T)((GLBase)new GLES30(this));
			}
		}
		
		public override void Dispose ()
		{
			throw new NotImplementedException ();
		}
		
	/*	public override ReadOnlyCollection<string> Extensions {
			get {
				throw new NotImplementedException ();
			}
		}*/
		
		public override IntPtr GetGLProcAddress (string name)
		{
			return glx.GetProcAddress(name);
		}
		
		public override void MakeCurrent ()
		{
			glx.MakeCurrent(this.display, this.handle, context);
		}
		
		public override void SwapBuffers ()
		{
			glx.SwapBuffers(this.display, this.handle);
		}
		
		public override Window Window {
			get {
				return this.window;
			}
		}

        public override void ChangeDisplayMode(DisplayMode mode, bool fullscreen)
        {
            throw new NotImplementedException();
        }

        public override DisplayMode DisplayMode
        {
            get
            {
                return new DisplayMode(640, 480, 60);
            }
            protected set
            {
                throw new NotImplementedException();
            }
        }

        public override bool IsFullscreen
        {
            get
            {
                throw new NotImplementedException();
            }
            protected set
            {
                throw new NotImplementedException();
            }
        }
	}
}

